/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package replica.transaction.stateMachine;

import java.util.List;
import java.util.logging.Logger;
import replica.ReplicaLoggerFactory;
import replica.stableMemory.StableFile;
import replica.transaction.FrontEndLogRegister;
import replica.transaction.LogRegister;
import replica.transaction.Operation;
import replica.transaction.ReplicaEntityInterface.Expected;
import replica.transaction.TransactionStatus;
import replica.transaction.stateMachine.InvalidTransitionException;

/**
 *
 * @author andrea
 */
public class FrontEndStateMachine implements StateMachine
{
    TransactionStatus myStatus;
    long nodeId;
    private Logger logger, transitionLogger;
    private LogRegister logRegister;
    private StableFile<TransactionStatus> stableFile;
    
    public FrontEndStateMachine(long nodeId)
    {
        this.nodeId=nodeId;
        this.logger=ReplicaLoggerFactory.getFrontEndLogger(nodeId, 0);
        this.transitionLogger=ReplicaLoggerFactory.getFrontEndStateMachineLogger(nodeId, 0);
        this.stableFile=new StableFile<TransactionStatus>("files/LogRegister/FrontEnd_"+this.nodeId+"/");
        logRegister=new FrontEndLogRegister(nodeId, this.stableFile);
    }
    
    @Override
    public void initializeStatus()
    {
        //TODO 
        //SE c'e' un logRegister register gia' su file 
        //(significa che il ReplicaManager ha avuto una precedente vita)
        //          allora crea TransactionStatus da file
        //ALTRIMENTI
        //          crea il TransactionStatus da capo
        TransactionStatus status= retrieveTransactionStatus();
        System.out.println("FrontEnd "+this.nodeId+", SDADUS: "+status);
        
        if(status==null)
            myStatus=TransactionStatus.buildOKStatus(this.nodeId, 0);
        else 
            myStatus=status;
        
        // PER DEBUG
        if(this.nodeId!=myStatus.getNodeId())
           throw new UnsupportedOperationException("this.nodeId="+this.nodeId+"; myStatus="+myStatus); 
        //FINE PER DEBUG
        
        System.out.println("FrontEnd "+this.nodeId+": FrontEndStateMachine: initialStatus="+this.getStatus());
    }
    
    @Override
    public void moveToStatus(TransactionStatus.Type statusType,Operation operation)
    {
        moveToStatus(statusType,operation,null);
    }
    
    
    public void moveToStatus(TransactionStatus.Type statusType,Operation operation,List<Long> readyReplicas)
    {
        //PER DEBUG
        try{if (myStatus==null || statusType==null || myStatus.getType()==null || 
                myStatus.getSerial()==null)
            throw new NullPointerException("myStatus="+myStatus+"; statusType="+statusType+
                    "myStatus.getType()="+myStatus.getType()+"; myStatus.getSerialNumber()="+
                    myStatus.getSerial());}
        catch(NullPointerException npe){npe.printStackTrace();throw npe;}
        //FINE PER DEBUG
        
        
        //Ci sono controlli sovrabbondanti per questioni di debug
        
        try{
            switch(statusType)
            {
                case PREPARE:
                    //PER DEBUG
                    if (operation==null || operation.getOperationType()==null || operation.getSerialNumber()==null)
                        throw new NullPointerException("operation="+operation+"; operation.getOperationType()="+
                                operation.getOperationType() +"; operation.getSerialNumber()="+
                                operation.getSerialNumber());
                    
                    if (
                            myStatus.getType() != TransactionStatus.Type.OK || 
                            operation.getOperationType() == Operation.OperationType.GETLIST || 
                            (
                                operation.getOperationType()!=Operation.OperationType.ALIGNMENT &&
                                !operation.getSerialNumber().equals(myStatus.getSerial() + 1)
                            )       ||
                            (
                                operation.getOperationType()==Operation.OperationType.ALIGNMENT &&
                                !operation.getSerialNumber().equals(myStatus.getSerial())
                            )
                       ) 
                        throw new InvalidTransitionException(myStatus, statusType, operation,this.nodeId,0);

                    //FINE PER DEBUG
                    
                    
                    
                    myStatus=TransactionStatus.buildStateMachineStatus(nodeId, operation, statusType);
                    logRegister.write(myStatus);
                    break;


                case ABORT: //The same as COMMIT
                case COMMIT:
                    if (
                            myStatus.getType()!=TransactionStatus.Type.PREPARE ||
                            operation.getOperationType()==Operation.OperationType.GETLIST ||
                            !operation.getSerialNumber().equals(myStatus.getSerial())
                    )
                        throw new InvalidTransitionException(myStatus,statusType,operation,this.nodeId,0);
                    
                    myStatus=TransactionStatus.buildDecisionStatus(nodeId, operation, statusType,readyReplicas);
                    logRegister.write(myStatus);
                    break;

                    
                    
                case OK:
                    //PER DEBUG
                    if (
                            myStatus.getType()!=TransactionStatus.Type.COMMIT &&
                            myStatus.getType()!=TransactionStatus.Type.ABORT &&
                            myStatus.getType()!=TransactionStatus.Type.IWANT_LIST &&
                            myStatus.getType()!=TransactionStatus.Type.GETLIST_REQ
                    )
                        throw new InvalidTransitionException(myStatus,statusType,this.nodeId);
                    System.out.println("!!!Mi e' stato chiesto di andare su OK. Il mio stato e' "
                            +myStatus);
                    //FDINE PER DEBUG
                    
                    long serial=myStatus.getSerial();
                    if (
                            myStatus.getType()==TransactionStatus.Type.ABORT &&
                            operation.getOperationType()!=Operation.OperationType.ALIGNMENT)
                        serial--;
                    //else don't change serial
                    myStatus=TransactionStatus.buildOKStatus(nodeId, serial);
                    logRegister.write(myStatus);
                    break;
                    
                case GETLIST_REQ:
                    if (myStatus.getType()!=TransactionStatus.Type.OK)
                        throw new InvalidTransitionException(myStatus,statusType,this.nodeId);
                    myStatus.setType(statusType);
                    logRegister.write(myStatus);
                    break;

                case IWANT_LIST:
                    if (myStatus.getType()!=TransactionStatus.Type.GETLIST_REQ)
                        throw new InvalidTransitionException(myStatus,statusType, this.nodeId);
                    myStatus.setType(statusType);
                    break;
                    
                default:
                    throw new InvalidTransitionException(myStatus,statusType,this.nodeId);
                    
            }
            
            transitionLogger.info("I am on status "+this.myStatus);
        }catch(InvalidTransitionException ise)
        {ise.printStackTrace();System.exit(-8732);}
    }
/*    
    public void changeStatusOnOperation(Operation operation)
    {
        try{
            switch(operation.getOperationType() )
            {
                case ENQUEUE: //The same as dequeue
                case DEQUEUE:
                        if (
                                myStatus.getType()==TransactionStatus.Type.OK &&
                                operation.getSerialNumber()==myStatus.getSerialNumber()+1
                        )
                        {
                            myStatus=new TransactionStatusSkeleton(nodeId,TransactionStatus.Type.PREPARE,
                                    myStatus.getSerialNumber()+1);
                            logRegister.write(myStatus);
                        }
                        else
                            throw new IllegalStateException("ERROR: my state is "+myStatus+" but you're "
                                        + "trying to make me execute operation "+operation);
                        break;
                    
               
                case GETLIST:
                        if (myStatus.getType()==TransactionStatus.Type.OK)
                        {
                            //I want change my serial
                            myStatus=new TransactionStatusSkeleton(
                                    nodeId,TransactionStatus.Type.GETLIST_REQ,myStatus.getSerialNumber());
                        }
                        else throw new IllegalStateException("ERROR: my state is "+myStatus+" but you're "
                                        + "trying to make me execute operation "+operation);
            }
        }catch(IllegalStateException te)
        {te.printStackTrace(); System.exit(-76342);}

    }
  
* 
*/
    
    
    @Override
    public Expected amIWaitingFor(TransactionStatus statusMsg)
    {
        Expected returnedValue=Expected.NO;
/*
        synchronized (waiting) {
            if (!waiting) {
                logger.logRegister(Level.WARNING, "received " + statusMsg + " but I'm not waiting for it."
                        + "Maybe it is late. I will ignore it");
                return Expected.LATE;
                //Se considerassi pure i messaggi arrivatimi in ritardo, potrei sbagliare.
                //Infatti, possibilmente gia' ho preso delle decisioni in base ai messaggi
                //fino a quel momento ricevuti. Queste decisioni potrebbero essere non più
                //consistenti se si considerasse nel novero dei messggi ricevuti anche questo
                //arrivato in ritardo. Per questo e' meglio considerare questo messaggio
                //come se non fosse mai arrivato, cioè trascurarlo.
            }
*/
        if (statusMsg.getType()==TransactionStatus.Type.WHAT_HAPPENED)
            return this.amIWaitingForWhatHappenedMsg(statusMsg);
//        if (statusMsg.getType()==TransactionStatus.Type.READY)
//            return this.amIWaitingForReadyMsg(statusMsg);
        
        switch(this.myStatus.getType())
        {
            
            case PREPARE:
                if (
                        (
                            statusMsg.getType()==TransactionStatus.Type.READY ||
                            statusMsg.getType()==TransactionStatus.Type.NOT_READY
                        ) &&
                        myStatus.getSerial().equals(statusMsg.getSerial() )
                )
                    returnedValue= Expected.YES;
                else
                    returnedValue= Expected.NO;
                break;
                
                
            case ABORT: //the same as COMMIT
            case COMMIT:
                if( 
                        statusMsg.getType()==TransactionStatus.Type.ACK &&
                        myStatus.getSerial().equals(statusMsg.getSerial() )
                )
                    returnedValue=Expected.YES;
                else
                    returnedValue=Expected.NO;
                break;
                
            case GETLIST_REQ:
                if (statusMsg.getType()==TransactionStatus.Type.MYSERIAL)
                    returnedValue=Expected.YES;
                else
                    returnedValue=Expected.STATUS_TYPE_UNEXPECTED;
                break;
                
            case IWANT_LIST:
                if(
                        statusMsg.getType()==TransactionStatus.Type.HEREIS_THE_LIST && 
                        statusMsg.getSerial()!=myStatus.getSerial()
                )
                    returnedValue=Expected.YES;
                else
                    returnedValue=Expected.NO;
                break;
                            
                
            
            default:
                logger.warning("My status is "+this.getStatus()+" and I received "+statusMsg+
                        ". I will ignore it");
            
        }
  
        if (returnedValue!=Expected.YES && statusMsg.getType()==TransactionStatus.Type.READY)
            return this.amIWaitingForReadyMsg(statusMsg);
        
        
        if (returnedValue!=Expected.YES)
            logger.warning("My status is "+myStatus+". I'm not waiting for message "+statusMsg);
        
        return returnedValue;
            
        
    }

    @Override
    public TransactionStatus getStatus() 
    {
        return myStatus;
    }
public TransactionStatus retrieveTransactionStatus()
    {
        TransactionStatus status=null;
        try{
            status=stableFile.read(TransactionStatus.class);
            if(status!=null){
                System.out.println("!!!NewStatus="+status.toString());
                return status;
            }
           
         }catch(NullPointerException e){
            return null;
            }
        return status;
            
        }

        private Expected amIWaitingForWhatHappenedMsg(TransactionStatus whatHappenedMsg)
        {
            if (myStatus.getSerial() < whatHappenedMsg.getSerial()-1)
                //PER DEBUG
                throw new UnsupportedOperationException("FrontEndStateMachine "+this.nodeId+
                        ". Ricevuto "+whatHappenedMsg+" ma mi trovo in "+myStatus+". Come e' possibile"
                        + " che il ReplicaManager mi abbia mandato un seriale cosi' alto?"); 
                //FINE PER DEBUG
            
            return Expected.WHAT_HAPPENED;
        }

    private Expected amIWaitingForReadyMsg(TransactionStatus readyMsg) 
    {
        if (myStatus.getSerial() > readyMsg.getSerial() + 1) //PER DEBUG
        {
            throw new UnsupportedOperationException("FrontEndStateMachine " + this.nodeId
                    + ". Ricevuto " + readyMsg + " ma mi trovo in " + myStatus + ". Come e' possibile"
                    + " che il ReplicaManager mi abbia mandato un seriale cosi' alto?");
        } //FINE PER DEBUG
        else {
            return Expected.LATE_READY;
        }
    }
}
